#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
扫描项目代码行数与字符数，并生成 Excel 报表。

运行前请确保同级目录存在 scan_config.json 配置文件。
输出文件：report_lines.xlsx

TODO(李四, 2025-08-26): 增加命令行参数覆盖配置文件
"""

from __future__ import with_statement  # 兼容 Python 2.5/2.6，3.x 无需此行

import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Tuple

# 若系统无 openpyxl，可降级到 xlsxwriter；此处兼容 3.1.2 无 pip 场景
try:
    import openpyxl
    HAS_OPENPYXL = True
except ImportError:
    HAS_OPENPYXL = False

try:
    import xlsxwriter
    HAS_XLSXWRITER = True
except ImportError:
    HAS_XLSXWRITER = False


# ------------------------------------------------------------
# 数据结构
# ------------------------------------------------------------
class FileStat:
    """记录单个文件的统计信息。"""

    def __init__(self, file_path: Path) -> None:
        self.path = file_path
        self.name = file_path.name
        self.size_bytes = file_path.stat().st_size
        self.size_mib = self.size_bytes / 1048576.0  # 预先换算
        self.lines = 0
        self.characters = 0


# ------------------------------------------------------------
# 配置读取
# ------------------------------------------------------------
def load_config(config_path: str = "scan_config.json") -> Dict:
    """
    读取 JSON 配置文件。

    返回
    ----
    Dict
        包含 project_path, extensions, max_file_size_mib
    """
    try:
        with open(config_path, "r", encoding="utf-8") as f:
            cfg = json.load(f)
    except (FileNotFoundError, ValueError) as e:
        sys.exit("配置文件读取失败：{0}".format(e))
    return cfg


# ------------------------------------------------------------
# 扫描逻辑
# ------------------------------------------------------------
def scan_project(cfg: Dict) -> List[FileStat]:
    """
    根据配置递归扫描目录，返回 FileStat 列表。

    跳过：
    - 非目标扩展名
    - 超过 max_file_size_mib 的文件
    - 二进制文件无法解码时忽略
    """
    root = Path(cfg["project_path"])
    extensions = set(ext.lower() for ext in cfg["extensions"])
    max_size_mib = cfg["max_file_size_mib"]
    max_size_bytes = int(max_size_mib * 1048576)

    results: List[FileStat] = []

    for file_path in root.rglob("*"):
        if not file_path.is_file():
            continue
        if file_path.suffix.lower() not in extensions:
            continue
        if file_path.stat().st_size > max_size_bytes:
            continue

        stat = FileStat(file_path)
        try:
            # 使用 latin-1 可读取任意字节流而不会抛异常，代价是字符数不绝对精确
            with file_path.open("r", encoding="utf-8", errors="ignore") as f:
                for line in f:
                    stat.lines += 1
                    stat.characters += len(line)
        except Exception:
            # 文件无法解码则跳过
            continue

        results.append(stat)

    return results


# ------------------------------------------------------------
# 报表输出
# ------------------------------------------------------------
def write_excel_report(stats: List[FileStat], output_path: str = "report_lines.xlsx") -> None:
    """
    将统计结果写入 Excel，按代码行数降序。

    表头：序号 | 文件名 | 路径 | 大小（MiB） | 代码行数
    """
    stats_sorted = sorted(stats, key=lambda x: x.lines, reverse=True)

    rows = [
        {
            "序号": idx + 1,
            "文件名": st.name,
            "路径": str(st.path.absolute()),
            "大小（MiB）": round(st.size_mib, 3),
            "代码行数": st.lines,
        }
        for idx, st in enumerate(stats_sorted)
    ]

    if HAS_OPENPYXL:
        # openpyxl 支持追加，此处直接覆盖
        import openpyxl as xl
        wb = xl.Workbook()
        ws = wb.active
        ws.title = "代码行数统计"
        ws.append(["序号", "文件名", "路径", "大小（MiB）", "代码行数"])
        for row in rows:
            ws.append([row["序号"], row["文件名"], row["路径"], row["大小（MiB）"], row["代码行数"]])
        wb.save(output_path)
    elif HAS_XLSXWRITER:
        workbook = xlsxwriter.Workbook(output_path)
        worksheet = workbook.add_worksheet("代码行数统计")
        headers = ["序号", "文件名", "路径", "大小（MiB）", "代码行数"]
        for col, header in enumerate(headers):
            worksheet.write(0, col, header)
        for row_idx, row in enumerate(rows, start=1):
            worksheet.write(row_idx, 0, row["序号"])
            worksheet.write(row_idx, 1, row["文件名"])
            worksheet.write(row_idx, 2, row["路径"])
            worksheet.write(row_idx, 3, row["大小（MiB）"])
            worksheet.write(row_idx, 4, row["代码行数"])
        workbook.close()
    else:
        # 无库可用时输出 CSV，兼容 3.1.2
        import csv
        with open("report_lines.csv", "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(
                f,
                fieldnames=["序号", "文件名", "路径", "大小（MiB）", "代码行数"]
            )
            writer.writeheader()
            writer.writerows(rows)
        print("未安装 Excel 库，已生成 CSV：report_lines.csv")
        return

    print("报表已生成：{0}".format(output_path))


# ------------------------------------------------------------
# 主流程
# ------------------------------------------------------------
def main() -> None:
    """主入口函数。"""
    cfg = load_config()
    stats = scan_project(cfg)

    if not stats:
        print("未找到符合条件的文件，报表未生成。")
        return

    write_excel_report(stats)


# ------------------------------------------------------------
# 脚本入口
# ------------------------------------------------------------
if __name__ == "__main__":
    main()